#!/usr/bin/env python3
from __future__ import annotations

import json
from pathlib import Path

import click

from trezorlib import firmware, toif


def minimum_header_len(spec, quiet):
    spec = spec.copy()
    spec["header_len"] = 512000
    reparsed = firmware.VendorHeader.SUBCON.parse(
        firmware.VendorHeader.SUBCON.build(spec)
    )
    data_length = reparsed._end_offset - reparsed._start_offset
    # data length + 65 for signatures, rounded up to nearest multiple of 512
    if not quiet:
        click.echo(f"Minimum header length: {data_length + 65} bytes.")
    return (data_length + 65 + 511) // 512 * 512


FilePath = click.Path(dir_okay=False, path_type=Path)


@click.command()
@click.argument("specfile", type=FilePath)
@click.option("-i", "--image", type=FilePath)
@click.option("-o", "--outfile", type=FilePath)
@click.option("-c", "--check", is_flag=True, help="Check but do not write header.")
@click.option("-q", "--quiet", is_flag=True, help="Do not print anything.")
def build_vendorheader(
    specfile: Path, image: Path | None, outfile: Path | None, check: bool, quiet: bool
):
    if quiet:
        echo = lambda *args, **kwargs: None
    else:
        echo = click.echo

    if not specfile.exists():
        raise click.ClickException(f"Spec file {specfile.name} does not exist.")

    if image is None:
        image = specfile.with_suffix(".toif")
    if not image.exists():
        raise click.ClickException(f"Image file {image.name} does not exist.")

    if outfile is None:
        vh_stem = specfile.stem
        if vh_stem.startswith("vendor_"):
            vh_stem = vh_stem[len("vendor_") :]
        outfile = specfile.parent / f"vendorheader_{vh_stem}_unsigned.bin"

    spec = json.loads(specfile.read_text())
    spec["pubkeys"] = [bytes.fromhex(k) for k in spec["pubkeys"]]
    spec["image"] = toif.ToifStruct.parse(image.read_bytes())
    spec["sigmask"] = 0
    spec["signature"] = b"\x00" * 64
    if spec["hw_model"] is None:
        spec["hw_model"] = b"\x00\x00\x00\x00"
    else:
        spec["hw_model"] = spec["hw_model"].encode("ascii")
    if not "fw_type" in spec.keys():
        spec["fw_type"] = 0

    min_length = minimum_header_len(spec, quiet)
    if "header_len" not in spec:
        spec["header_len"] = min_length
    elif spec["header_len"] < min_length:
        raise click.ClickException(
            f"Specified header_len {spec['header_len']} too low. "
            f"Minimum allowable value is {min_length}."
        )
    elif spec["header_len"] == min_length:
        echo(f"{specfile.name}: Header has correct length.")
    else:
        echo(
            f"{specfile.name}: Extending header ({min_length} bytes) to {spec['header_len']} bytes."
        )

    if spec["header_len"] % 512 != 0:
        raise click.ClickException("Invalid header_len: must be a multiple of 512")

    vh_bytes = firmware.VendorHeader.SUBCON.build(spec)
    if check:
        if not outfile.exists():
            raise click.ClickException(f"Header file {outfile.name} does not exist.")
        outfile_bytes = outfile.read_bytes()
        if outfile_bytes != vh_bytes:
            raise click.ClickException(
                f"Header file {outfile.name} differs from expected header."
            )
    else:
        outfile.write_bytes(firmware.VendorHeader.SUBCON.build(spec))


if __name__ == "__main__":
    build_vendorheader()
